package com.lap.framework.resources;

import com.lap.framework.domain.Resources;
import com.lap.framework.exception.BizException;
import com.lap.framework.resources.utils.Constants;
import com.lap.framework.resources.utils.MethodEnum;
import com.lap.framework.resources.utils.UriUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.Getter;
import lombok.Setter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;

/**
 * <pre>
 * 解析API支持类
 * </pre>
 *
 * @author Shuisheng.Lao
 * @version 1.0.0
 */
public final class ResourcesSupport {
    private static final Logger LOGGER = LoggerFactory.getLogger(ResourcesSupport.class);

    private ResourcesSupport() {
    }

    /**
     * <pre>
     * 获取树形资源,后续需要做缓存,这个递归,比较慢
     * </pre>
     *
     * @param dataList 数据
     * @return 资源信息
     */
    public List<Resources> getRoot(List<Resources> dataList) {
        List<Resources> result = new LinkedList<>();
        if (Objects.nonNull(dataList)) {
            for (Resources resources : dataList) {
                if (UriUtils.isEmpty(resources.getParentUriCode())) {
                    result.add(resources);
                    getSon(resources, dataList);
                }
            }
        }
        return result;
    }

    /**
     * <pre>
     *  递归获取子资源
     * </pre>
     *
     * @param root     根资源
     * @param dataList 资源数据
     * @return 子资源
     */
    private Resources getSon(Resources root, List<Resources> dataList) {
        if (Constants.IS_LEAF.equals(root.getLeafFlag())) {
            return root;
        }
        for (Resources resources : dataList) {
            if (root.getUriCode().equals(resources.getParentUriCode())) {
                if (Objects.isNull(root.getSonList())) {
                    root.setSonList(new LinkedList<>());
                }
                root.getSonList().add(getSon(resources, dataList));
            }
        }
        return root;
    }

    /**
     * 1、根据包读取下面所有的文件,递归
     * 2、获取文件,根据文件名,去除.class结尾,
     * 3、获取 RestController注解,获取.
     * 4、解析类内容,关键点 Api,RequestMapping,
     * 5、解析函数,关键点, XXMapping.
     * 6、需要主要 函数的地址 类 RequestMapping+XXMapping
     *
     * @param module   模块
     * @param pathName 包名
     * @return 资源信息
     */
    public List<Resources> getTreeResources(String module, String name, String pathName) {
        List<ResourcesFile> fileList = new LinkedList<>();
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        List<Resources> dataList = Collections.emptyList();
        try {
            Resource[] resources = resolver.getResources(pathName.replace(".", "/"));
            for (Resource e : resources) {
                File file = e.getFile();
                readFile(fileList, pathName, file);
                List<Class<?>> classList = getClassFile(fileList);
                List<Class<?>> apiList = getApi(classList);
                dataList = parseTreeClass(module, apiList);
            }
        } catch (IOException e) {
            LOGGER.error(e.getLocalizedMessage(), e);
        }
        return dataList;
    }

    /**
     * <pre>
     *  获取资源
     * </pre>
     *
     * @param module      模块
     * @param name        项目名
     * @param packageName 路径名
     * @return 资源信息
     */
    public List<Resources> getResources(String module, String name, String packageName) {
        List<ResourcesFile> fileList = new LinkedList<>();
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        List<Resources> dataList = Collections.emptyList();
        try {
            Resource[] resources = resolver.getResources(packageName.replace(".", "/"));
            for (Resource e : resources) {
                File file = e.getFile();
                readFile(fileList, packageName, file);
                List<Class<?>> classList = getClassFile(fileList);
                List<Class<?>> apiList = getApi(classList);
                dataList = parseClass(module, name, apiList);
            }
        } catch (IOException e) {
            throw new BizException("1404");
        }
        return dataList;
    }

    /**
     * <pre>
     *  读取包目录下的文件,递归读取文件
     * </pre>
     *
     * @param fileList    文件集合
     * @param packageName 包名
     * @param file        文件
     */
    private void readFile(List<ResourcesFile> fileList, String packageName, File file) {
        File[] files = file.listFiles();
        if (Objects.isNull(files)) {
            return;
        }
        Stream.of(files).forEach(e -> {
            if (e.isDirectory()) {
                StringBuilder packageBuffer = new StringBuilder();
                packageBuffer.append(packageName);
                if (!packageName.endsWith(".")) {
                    packageBuffer.append(".");
                }
                packageBuffer.append(e.getName());
                readFile(fileList, packageBuffer.toString(), e);
            } else {
                ResourcesFile f = new ResourcesFile();
                f.setName(e.getName());
                f.setPackageName(packageName);
                f.setFile(e);
                fileList.add(f);
            }
        });
    }

    /**
     * <pre>
     *  解析所有的 CLASS 文件,
     *  有可能有内部类,所以这里不但解析 .class文件, 还需要解析 $文件
     * </pre>
     *
     * @param fileList 文件
     * @return 所有的类集合
     */
    private List<Class<?>> getClassFile(List<ResourcesFile> fileList) {
        List<Class<?>> classList = new LinkedList<>();
        fileList.forEach(e -> {
            if (e.getName().contains(".class")) {
                String fileName = e.getName();
                if (fileName.contains("$")) {
                    fileName = e.getName().substring(0, e.getName().indexOf("$"));
                } else {
                    fileName = e.getName().substring(0, e.getName().indexOf(".class"));
                }
                try {
                    Class<?> clazz = Class.forName(e.getPackageName() + "." + fileName);
                    classList.add(clazz);
                } catch (ClassNotFoundException ex) {
                    LOGGER.warn("class file is not find", e);
                }
            }
        });
        return classList;
    }

    /**
     * <pre>
     * 解析仅仅是 Controller 类的文件
     * </pre>
     *
     * @param classList 所有的类
     * @return 所有的控制类
     */
    private List<Class<?>> getApi(List<Class<?>> classList) {
        List<Class<?>> result = new LinkedList<>();
        classList.forEach(e -> {
            if (e.isAnnotationPresent(RestController.class)) {
                result.add(e);
            }
        });
        return result;
    }

    /**
     * 解析没有树形的集合
     *
     * @param module   模块
     * @param name     名称
     * @param dataList 数据
     * @return 资源信息
     */
    private List<Resources> parseClass(String module, String name, List<Class<?>> dataList) {
        List<Resources> list = new LinkedList<>();
        dataList.forEach(e -> {
            Resources parent = new Resources();
            parent.setModule(module);
            parent.setParentUriCode(module);
            list.add(parent);
            initClass(parent, e);
            parseMethod(module, parent, e);
            list.addAll(parent.getSonList());
            parent.setSonList(null);
        });
        list.add(getModule(module, name));
        return list;
    }

    /**
     * <pre>
     *  初始化模块,来源于项目配置
     * </pre>
     *
     * @param module 模块编码
     * @param name   模块名称
     * @return 资源信息
     */
    private Resources getModule(String module, String name) {
        Resources parent = new Resources();
        parent.setModule(module);
        parent.setUri(module);
        parent.setUriCode(module);
        parent.setMethod("-");
        parent.setName(name);
        parent.setDataFlag(Constants.DATA_FLAG);
        parent.setLeafFlag(Constants.LEAF);
        parent.setTypeFlag(Constants.MODULE_FLAG);
        return parent;
    }

    /**
     * <pre>
     *  解析类,如果存在,获取 RequestMapping
     * </pre>
     *
     * @param module   模块
     * @param dataList 所有的控制类
     */
    private List<Resources> parseTreeClass(String module, List<Class<?>> dataList) {
        List<Resources> list = new LinkedList<>();
        dataList.forEach(e -> {
            Resources parent = new Resources();
            list.add(parent);
            parent.setModule(module);
            parent.setParentUriCode(module);
            initClass(parent, e);
            parseMethod(module, parent, e);
        });
        return list;
    }

    /**
     * <pre>
     *  处理资源类
     * </pre>
     *
     * @param parent 父资源
     * @param clazz  类型
     */
    private void initClass(Resources parent, Class<?> clazz) {
        StringBuilder restUri = new StringBuilder();
        Class<?> parentClass = clazz.getSuperclass();
        if (parentClass.isAnnotationPresent(RequestMapping.class)) {
            RequestMapping parentMapping = parentClass.getAnnotation(RequestMapping.class);
            restUri.append(UriUtils.getUri(parentMapping.value()));
        }
        if (clazz.isAnnotationPresent(RequestMapping.class)) {
            RequestMapping mapping = clazz.getAnnotation(RequestMapping.class);
            restUri.append(UriUtils.getUri(mapping.value()));
        }

        if (clazz.isAnnotationPresent(Api.class)) {
            Api api = clazz.getAnnotation(Api.class);
            if (Objects.nonNull(api)) {
                String apiComment = api.value();
                parent.setName(apiComment);
            }
        }
        parent.setUri(restUri.toString());
        parent.setUriCode(UriUtils.getUriCode(parent.getModule(), UriUtils.getTypeUri(clazz.getSimpleName()), parent.getMethod()));
        parent.setDataFlag(Constants.DATA_FLAG);
        parent.setMethod("-");
        parent.setLeafFlag(Constants.LEAF);
        parent.setTypeFlag(Constants.SUB_MODULE_FLAG);
    }

    /**
     * <pre>
     *  解析方法
     * </pre>
     *
     * @param module 模块
     * @param parent 父资源
     * @param clazz  类型
     */
    private void parseMethod(String module, Resources parent, Class<?> clazz) {
        Method[] metods = clazz.getDeclaredMethods();
        if (Objects.nonNull(metods)) {
            List<Resources> sonList = new LinkedList<>();
            parent.setSonList(sonList);
            Stream.of(metods).forEach(e -> {
                Resources son = MethodEnum.MappingMethod.getResource(e, parent);
                if (Objects.nonNull(son)) {
                    sonList.add(son);
                    son.setModule(module);
                    if (e.isAnnotationPresent(ApiOperation.class)) {
                        ApiOperation apiOperation = e.getAnnotation(ApiOperation.class);
                        son.setName(Objects.nonNull(apiOperation) ? apiOperation.value() : son.getUri());
                    } else {
                        son.setName(son.getUri());
                    }
                }
            });
        }
    }

    /**
     * 枚举工厂类
     *
     * @author Shuisheng.Lao
     * @version 1.0.0
     */
    public enum Factory {
        /**
         * 单例
         */
        Instance;
        /**
         * 内部属性
         */
        private final ResourcesSupport instance;

        Factory() {
            instance = new ResourcesSupport();
        }

        public ResourcesSupport instance() {
            return instance;
        }

    }

    /**
     * <pre>
     *  内部类,仅仅处理类需要,所以并不需要外部定义
     * </pre>
     *
     * @author Shuisheng.Lao
     * @version 1.0.0
     */
    @Getter
    @Setter
    private static class ResourcesFile {
        /**
         * 文件名
         */
        private String name;
        /**
         * 包路径
         */
        private String packageName;
        /**
         * 源文件
         */
        private File file;

    }

}
